/**
  *********************************************************************************
  * @file    dht11.c 
  * @author  xxx@126.com
  * @version V1.0.0   主版本号.次版本号.修订版本号
  * @date    2024/12/04
  * @brief   利用DHT11温湿度传感器获取温湿度数据，并通过MCU的UART输出
  *********************************************************************************
  *	 
  *  01.DHT11传感器采用单总线协议，其实是半双工的串行接口
  *
  *  CopyRight (c)  Your Name    AllRight Reserved
  *********************************************************************************
  */

/* Includes 用于记录工程所需要的头文件	------------------------------------------*/
#include "dht11.h"
/* Private typedef 用于记录工程的数据类型的二次命名	------------------------------*/
/* Private define用于记录工程中自定义的数据类型	----------------------------------*/
/* Private macro 用于记录工程的自定义的宏	--------------------------------------*/


/**
  * @brief  DHT11的初始化
  * @param  None
  * @retval None
  */
void DHT11_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	//打开GPIOG时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	
	//配置引脚 + 初始化
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;			//输出功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
	GPIO_Init(GPIOC, &GPIO_InitStructure);

}

/**
  * @brief  设置DHT11的引脚模式
  * @param  mode ：输出模式 GPIO_Mode_Out_PP  or  输入模式 GPIO_Mode_IN_FLOATING
  * @retval None
  * @note   MCU发送开始信号时，应该处于输出模式
  */
void DHT11_SetPinMode(GPIOMode_TypeDef mode)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	//打开GPIOG时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	
	//配置引脚 + 初始化
	GPIO_InitStructure.GPIO_Mode  = mode;				//引脚模式
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
	GPIO_Init(GPIOC, &GPIO_InitStructure);

}
/**
  * @brief  DHT11的开始信号
  * @param  None
  * @retval None               2 
  */
void DHT11_Start(void)
{
	//1.设置引脚为输出模式
	DHT11_SetPinMode(GPIO_Mode_Out_PP);
	
	//2.把引脚电平拉低并持续 > 18ms
	GPIO_ResetBits(GPIOC,GPIO_Pin_9);
	delay_ms(20);
	
	//3.设置引脚为输入模式
	DHT11_SetPinMode(GPIO_Mode_IN_FLOATING);
}
/**
  * @brief  DHT11的响应信号
  * @param  None
  * @retval true 响应  false 未响应
  * @note   为了避免传感器异常出现死循环，所以应该人为添加超时机制
  */
bool DHT11_IsACK(void)
{
	uint32_t cnt = 0; //用于作为计时器  单位是us
	
	//1.MCU等待DHT11把引脚电平拉低  假设超时时间是100us
	while( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_9) == 1 && cnt < 100 )
	{
		delay_us(1);
		cnt++;
	}
	
	if(cnt>=100)
	{
		return false;  //说明未响应
	}
	
	//2.MCU等待DHT11把引脚电平拉高  假设超时时间是100us
	cnt = 0;
	while( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_9) == 0 && cnt < 100 )
	{
		delay_us(1);
		cnt++;
	}
	
	if(cnt>=100)
		return false;  //说明未响应
	else
		return true;   //说明响应了
}
/**
  * @brief  DHT11读取1bit
  * @param  None
  * @retval 返回 0 (0000000 0) or 1 (0000000 1)
  * @note   该函数返回的是读取的bit的状态  但是把bit存储在字节的最低位  
  */
uint8_t  DHT11_ReadBit(void)
{
	uint32_t cnt = 0; //用于作为计时器  单位是us
	
	//1.MCU等待DHT11把引脚电平拉低  
	while( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_9) == 1 && cnt < 100 );
	
	cnt=0;
	
	//2.MCU等待DHT11把引脚电平拉高
	while( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_9) == 0 && cnt < 100 );
	
	//3.出现高电平后开始延时 延时时间 >27us  &&  <74us
	delay_us(40);
	
	if( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_9) == 1 )
		return 1;
	else
		return 0;
}
/**
  * @brief  DHT11读取1bit
  * @param  None
  * @retval 返回 data
  * @note   DHT11返回的数据是MSB
  */
uint8_t  DHT11_ReadByte(void)
{
	uint8_t data = 0; //用于存储读取的8bit数据 
	uint8_t i = 0;    //为了循环
	
	for(i=0;i<8;i++)
	{
		data <<= 1; 
		data |= DHT11_ReadBit();  	 
	}
	
	return data;
}
/**
  * @brief  DHT11获取温湿度数据
  * @param  buf:存储数据的缓冲区地址
  * @retval 返回true  成功  false  失败
  * @note   None
  */
bool DHT11_GetVal(uint8_t *buf)
{
	//1.MCU发送开始信号
	DHT11_Start();
	
	//2.判断DHT11是否响应
	if( DHT11_IsACK() != true )
	{
		printf("失败\r\n");
		return false; //获取失败，原因是DHT11未响应
	}
	else
	{
		//3.循环接收40bit
		for(int i = 0;i < 5;i++)
		{
			buf[i] = DHT11_ReadByte();
		}
		
		//4.进行数据校验
		if( buf[0] + buf[1] + buf[2] +buf[3] != buf[4] )
		{
			return false; //获取失败，原因是校验失败
		}
	}
	
	return true;
}

